﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Text;
using AForge.Imaging;
using AForge.Math;
using AForge.DebuggerVisualizers;
using System.Collections;
using System.Drawing.Imaging;
using AForge.Imaging.Filters;
using AForge;
using Bildverarbeitung;
using System;
using System.Drawing;
using Bildverarbeitung;

namespace CG_Door_Feature_Extraction.CGFilter
{
    class EnglertMichaelFilter : Bildverarbeitung.IFilter
    {
        /// <summary>
        /// Gets the summerized difference from average of all images.
        /// </summary>
        /// <param name="images">Given images.</param>
        /// <returns>The summerized difference.</returns>
        public Bitmap GetImageWithDoor(Bitmap image)
        {
            return filter(image);
        }

        /// <summary>
        /// Return the author´s name
        /// Please use NachnameVorname (without spaces).
        /// </summary>
        /// <returns>author´s name</returns>
        public String GetName()
        {
            return ("EnglertMichael");
        }



        /// <summary>
        /// Führt eine Kanten - Ecken Analyse durch
        /// </summary>
        /// <param name="bitmap"></param>
        /// <returns></returns>
        public Bitmap filter(Bitmap bitmap)
        {
            // Bildbereiche innerhalb derer die Kanten erkannt werden sollen
            int[,] imageParts = new int[,]{{0,    400, 139, 400},   // Linke untere Kante
                                           {140,   65, 310, 615},   // Linke Tür
                                           {620,  110, 300, 280},   // Pinwand
                                           {445,  600, 555, 150},   // Leiste am Boden
                                           {1000,  65, 280, 625}};  // Rechte Tür

            return filterRectangles(bitmap, imageParts);
        }



        /// <summary>
        /// Macht alle Kanten und Eckpunkte des übergebenen Bildes sichtbar
        /// </summary>
        /// <param name="bm">Bitmap in der alle Kanten und Punkte gesucht werden sollen</param>
        public Bitmap filterRectangles(Bitmap bm, int[,] imageParts)
        {
            // Liste mit den gefundenen Rectangles
            ArrayList rectangles = new ArrayList();

            // Bitmap die zurück gegeben werden soll
            Bitmap bm2 = (Bitmap)bm.Clone();
            //Bitmap bm2 = new Bitmap(bm.Width, bm.Height);

            // Vorverarbeitung des Bildes
            foreWork(ref bm);

            // Aus den Bildteilen die Kanten bestimmen
            for (int i = 0; i < imageParts.GetLength(0); i++)
            {
                // Bitmap über bestimmte Bildbereiche erzeugen und in Hugh-Raum transformieren
                Bitmap tempBitmap = bm.Clone(new Rectangle(imageParts[i, 0], imageParts[i, 1],
                                                           imageParts[i, 2], imageParts[i, 3]),
                                                           bm.PixelFormat);

                HoughLineTransformation lineTransform = new HoughLineTransformation();
                // apply Hough line transofrm
                lineTransform.ProcessImage(tempBitmap);
                Bitmap houghLineImage = lineTransform.ToBitmap();
                // get lines using relative intensity
                HoughLine[] lines = lineTransform.GetLinesByRelativeIntensity(0.5);

                BitmapData bData = bm2.LockBits(new Rectangle(imageParts[i, 0], imageParts[i, 1],
                                                              imageParts[i, 2], imageParts[i, 3]),
                                                              ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
                // Unmanaged Image erzeugen um Hough-Transformation durchführen zu können
                UnmanagedImage image = new UnmanagedImage(bData);

                // Zeichnen der gefundenen Linien
                houghTransform(lines, image);

                bm2.UnlockBits(bData);
            }

            // Eckpunkte auslesen 
            ArrayList corners = CornerDetector(bm2);

            // Zu allen Punkten die Rechtecke ermitteln
            foreach (IntPoint ip in corners)
            {
                IntPoint rightest = recursiveFindRightestConnection(bm2, ip);
                IntPoint downest = recursiveFindBottomConnection(bm2, ip);
                rectangles.Add(new Rectangle(ip.X, ip.Y, rightest.X - ip.X, downest.Y - ip.Y));
            }

            Rectangle biggestRect = getMaxRectangle(rectangles);

            // Zeichnen des Bildes
            printRect(biggestRect, ref bm2);

            // Bild zeichnen
            return bm2;
        }



        /// <summary>
        /// Zeichnet ein beliebiges Rectangle in das übergebene Bild
        /// </summary>
        /// <param name="rectangle">Rectangle das gezeichnet werden soll</param>
        /// <param name="bmp">Bitmap in die gezeichnet werden soll</param>
        public void printRect(Rectangle rectangle, ref Bitmap bmp)
        {
            for (int y = rectangle.Y + 5; y < rectangle.Y + rectangle.Height; y++)
            {
                for (int x = rectangle.X + 3; x < rectangle.X + rectangle.Width - 1; x++)
                {
                    bmp.SetPixel(x, y, Color.Yellow);
                }
            }
        }



        /// <summary>
        /// Sucht das größte Rectangle der übergebenen Rectangle-Liste
        /// </summary>
        /// <param name="rectangles">Liste mit den Rectangles</param>
        /// <returns>größtes Rectangle</returns>
        public Rectangle getMaxRectangle(ArrayList rectangles)
        {
            Rectangle rectReturn = new Rectangle(0, 0, 0, 0);

            foreach (Rectangle r in rectangles)
            {
                if ((rectReturn.Height * rectReturn.Width) < (r.Height * r.Width))
                {
                    rectReturn.Height = r.Height;
                    rectReturn.Width = r.Width;
                    rectReturn.X = r.X;
                    rectReturn.Y = r.Y;
                }
            }

            return rectReturn;
        }



        /// <summary>
        /// Rekursive Suche nach dem rechtesten verbundenen Punkt
        /// </summary>
        /// <param name="bmp"></param>
        /// <param name="ip"></param>
        /// <returns></returns>
        public IntPoint recursiveFindRightestConnection(Bitmap bmp, IntPoint ip)
        {
            IntPoint ret;
            IntPoint point = isRightConnected(bmp, ip);
            if (point.X == 0 && point.Y == 0)
            {
                return ip;
            }
            else
                ret = recursiveFindRightestConnection(bmp, point);

            return ret;
        }



        /// <summary>
        /// Sucht der übergebene Punkt mit einem weiteren verbunden ist
        /// </summary>
        /// <param name="bmp"></param>
        /// <param name="startPoint"></param>
        /// <returns></returns>
        public IntPoint isRightConnected(Bitmap bmp, IntPoint startPoint)
        {
            IntPoint tempPoint = tempPoint = new IntPoint(startPoint.X + 1, startPoint.Y);
            Color[] matrix = new Color[3];
            int xtemp = tempPoint.X;
            for (; xtemp < bmp.Width - 1; xtemp++)
            {
                matrix[0] = bmp.GetPixel(tempPoint.X + 1, tempPoint.Y - 1);
                matrix[1] = bmp.GetPixel(tempPoint.X + 1, tempPoint.Y);
                matrix[2] = bmp.GetPixel(tempPoint.X + 1, tempPoint.Y + 1);

                if (matrix[0].R == Color.Lime.R && matrix[0].G == Color.Lime.G && matrix[0].B == Color.Lime.B)
                {
                    tempPoint.X += 1;
                    tempPoint.Y -= 1;
                }
                else if (matrix[1].R == Color.Lime.R && matrix[1].G == Color.Lime.G && matrix[1].B == Color.Lime.B)
                    tempPoint.X += 1;
                else if (matrix[2].R == Color.Lime.R && matrix[2].G == Color.Lime.G && matrix[2].B == Color.Lime.B)
                {
                    tempPoint.X += 1;
                    tempPoint.Y += 1;
                }
                else
                    break;
            }

            if (matrix[1].R == Color.Red.R && matrix[1].G == Color.Red.G && matrix[1].B == Color.Red.B)
                return new IntPoint(xtemp + 2, tempPoint.Y);

            return new IntPoint();
        }



        /// <summary>
        /// Rekursive Suche nach dem rechtesten verbundenen Punkt
        /// </summary>
        /// <param name="bmp"></param>
        /// <param name="ip"></param>
        /// <returns></returns>
        public IntPoint recursiveFindBottomConnection(Bitmap bmp, IntPoint ip)
        {
            IntPoint ret;
            IntPoint point = isBottomConnected(bmp, ip);
            if (point.X == 0 && point.Y == 0)
            {
                return ip;
            }
            else
                ret = recursiveFindBottomConnection(bmp, point);

            return ret;
        }



        /// <summary>
        /// Sucht ob der übergebene Punkt mit einem weiteren verbunden ist
        /// </summary>
        /// <param name="bmp"></param>
        /// <param name="startPoint"></param>
        /// <returns></returns>
        public IntPoint isBottomConnected(Bitmap bmp, IntPoint startPoint)
        {
            IntPoint tempPoint = tempPoint = new IntPoint(startPoint.X, startPoint.Y + 1);
            Color[] matrix = new Color[3];
            int ytemp = tempPoint.Y;
            for (; ytemp < bmp.Height - 1; ytemp++)
            {
                matrix[0] = bmp.GetPixel(tempPoint.X - 1, tempPoint.Y + 1);
                matrix[1] = bmp.GetPixel(tempPoint.X, tempPoint.Y + 1);
                matrix[2] = bmp.GetPixel(tempPoint.X + 1, tempPoint.Y + 1);

                if (matrix[0].R == Color.Lime.R && matrix[0].G == Color.Lime.G && matrix[0].B == Color.Lime.B)
                {
                    tempPoint.X -= 1;
                    tempPoint.Y += 1;
                }
                else if (matrix[1].R == Color.Lime.R && matrix[1].G == Color.Lime.G && matrix[1].B == Color.Lime.B)
                    tempPoint.Y += 1;
                else if (matrix[2].R == Color.Lime.R && matrix[2].G == Color.Lime.G && matrix[2].B == Color.Lime.B)
                {
                    tempPoint.X += 1;
                    tempPoint.Y += 1;
                }
                else
                    break;
            }

            if (matrix[1].R == Color.Red.R && matrix[1].G == Color.Red.G && matrix[1].B == Color.Red.B)
                return new IntPoint(tempPoint.X - 1, ytemp + 2);

            return new IntPoint();
        }



        /// <summary>
        /// Vorverarbeitung im Bild durchführen
        /// </summary>
        /// <param name="bm">Referenz auf die Bitmap die vorverarbeitet werden soll</param>
        public void foreWork(ref Bitmap bm)
        {
            // Vorverarbeitung mit Graustufen und CannyEdgeDetector und Threshold
            Grayscale filter = new Grayscale(0.2125, 0.2154, 0.2721);
            bm = filter.Apply(bm);
            CannyEdgeDetector filter3 = new CannyEdgeDetector();
            filter3.HighThreshold = 20;
            filter3.LowThreshold = 10;
            filter3.GaussianSize = 2;
            filter3.ApplyInPlace(bm);
            Threshold filter2 = new Threshold(30);
            filter2.ApplyInPlace(bm);
        }



        /// <summary>
        /// Ermittelt alle Eckpunkte und markiert diese Color.RED
        /// </summary>
        /// <param name="bmp"></param>
        /// <returns></returns>
        public ArrayList CornerDetector(Bitmap bmp)
        {
            ArrayList list = new ArrayList();

            for (int y = 1; y < bmp.Height - 1; y++)
            {
                for (int x = 1; x < bmp.Width - 1; x++)
                {
                    Color[] matrix = new Color[9];
                    matrix[0] = bmp.GetPixel(x, y);           // center

                    if (matrix[0].R == Color.Lime.R && matrix[0].G == Color.Lime.G && matrix[0].B == Color.Lime.B)
                    {
                        matrix[1] = bmp.GetPixel(x - 1, y - 1);   // top-left
                        matrix[2] = bmp.GetPixel(x, y - 1);       // top
                        matrix[3] = bmp.GetPixel(x + 1, y - 1);   // top-right
                        matrix[4] = bmp.GetPixel(x - 1, y);       // center-left
                        matrix[5] = bmp.GetPixel(x + 1, y);       // center-right
                        matrix[6] = bmp.GetPixel(x - 1, y + 1);   // bottom-left
                        matrix[7] = bmp.GetPixel(x, y + 1);       // bottom
                        matrix[8] = bmp.GetPixel(x + 1, y + 1);   // bottom-right

                        // Zähler für die Grünen Pixel in der Matrix
                        int zaehlerTop = 0, zaehlerCenter = 0, zaehlerBottom = 0;

                        for (int i = 1; i < matrix.Length; i++)
                        {
                            if (matrix[i].R == Color.Lime.R && matrix[i].G == Color.Lime.G && matrix[i].B == Color.Lime.B)
                            {
                                if (i < 4)
                                    zaehlerTop++;
                                else if (i < 6)
                                    zaehlerCenter++;
                                else
                                    zaehlerBottom++;
                            }
                        }

                        if ((zaehlerTop + zaehlerCenter + zaehlerBottom) == 4 &&
                            (zaehlerTop <= 2) && (zaehlerCenter <= 2) && (zaehlerBottom <= 2))
                        {
                            list.Add(new IntPoint(x, y));
                            bmp.SetPixel(x, y, Color.Red);
                            bmp.SetPixel(x - 1, y - 1, Color.Red);
                            bmp.SetPixel(x, y - 1, Color.Red);
                            bmp.SetPixel(x + 1, y - 1, Color.Red);
                            bmp.SetPixel(x - 1, y, Color.Red);
                            bmp.SetPixel(x + 1, y, Color.Red);
                            bmp.SetPixel(x - 1, y + 1, Color.Red);
                            bmp.SetPixel(x, y + 1, Color.Red);
                            bmp.SetPixel(x + 1, y + 1, Color.Red);
                        }
                    }
                }
            }

            return list;
        }



        /// <summary>
        /// Zeichnet alle übergebenen Linien des Hough-Raumes im übergebenen Image in der Farbe Color.LIME
        /// </summary>
        /// <param name="lines">Alle zu zeichnenden Linien des Hough-Raumes</param>
        /// <param name="image">UnmanagedImage in das gezeichnet werden soll</param>
        public void houghTransform(HoughLine[] lines, UnmanagedImage image)
        {
            foreach (HoughLine line in lines)
            {
                // get line's radius and theta values
                int r = line.Radius;
                double t = line.Theta;

                // check if line is in lower part of the image
                if (r < 0)
                {
                    t += 180;
                    r = -r;
                }

                // convert degrees to radians
                t = (t / 180) * Math.PI;

                // get image centers (all coordinate are measured relative to center)
                int w2 = image.Width / 2;
                int h2 = image.Height / 2;

                double x0 = 0, x1 = 0, y0 = 0, y1 = 0;

                if (line.Theta != 0)
                {
                    // none-vertical line
                    x0 = -w2; // most left point
                    x1 = w2;  // most right point

                    // calculate corresponding y values
                    y0 = (-Math.Cos(t) * x0 + r) / Math.Sin(t);
                    y1 = (-Math.Cos(t) * x1 + r) / Math.Sin(t);
                }
                else
                {
                    // vertical line
                    x0 = line.Radius;
                    x1 = line.Radius;

                    y0 = h2;
                    y1 = -h2;
                }

                // draw line on the image
                Drawing.Line(image,
                             new IntPoint((int)x0 + w2, h2 - (int)y0),
                             new IntPoint((int)x1 + w2, h2 - (int)y1),
                             Color.Lime);

            }
        }
    }
}
